package com.DesignPattern;

import com.DesignPattern.Annotations.Autowired;
import com.DesignPattern.Annotations.Component;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * IOC 容器
 *
 * @Author you-name
 * @Date 2022/4/7
 */
public class IOC {
    // beanFactory 要声明为类变量，因为它不能被GC回收
    private static HashMap<String, Object> beanFactory = new HashMap<>();

    /**
     * 随着MyIOC类被加载到内存进行初始化，就会执行其静态代码块
     */
    static {
        init();
    }

    /**
     * 获取BeanFactory
     *
     * @return
     */
    public static HashMap<String, Object> getBeanFactory() {
        return beanFactory;
    }

    /**
     * 根据全类名更新BeanFactory中的bean
     *
     * @param typeName
     * @param proxyInstance
     */
    public static void updateBeanFromBeanFactory(String typeName, Object proxyInstance) {
        beanFactory.put(typeName, proxyInstance);
    }

    /**
     * 通过全类名获得对应的实例
     *
     * @param completeClassName
     * @return
     */
    public static Object getBean(String completeClassName) {
        return beanFactory.get(completeClassName);
    }

    /**
     * 初始化
     */
    private static void init() {
        HashMap<String, Class> loadedClazzList;//<全类名, Class对象>
        try {
            //1.加载指定的类
            File file = new File("H:\\IocStudy");//TODO ！！！这里写死了路径不合适，可以做改进
            loadedClazzList = loadAllClazz(file);

            //2.实例化并放入 IOC 容器中，对于那些有注解的类，做实例化
            newInstance(loadedClazzList);

            //3.完成依赖注入
            autoWired();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 对BeanFactory中管理的所有bean完成依赖注入。
     * 交给IOC容器管理的类，需要注入成员变量，如果该成员变量是自定义的类，该类也是需要交给IOC容器管理的。
     */
    private static void autoWired() throws IllegalAccessException {
        for (Map.Entry<String, Object> entry : beanFactory.entrySet()) {
            //getFields():只能获取到运行时类中及其父类中声明为public的属性；getDeclaredFields():获取运行时类本身声明的所有的属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                Annotation[] annotations = field.getAnnotations();
                for (Annotation annotation : annotations) {
                    if (annotation.annotationType() == Autowired.class) {
                        //从beanFactory中找到相应的bean,赋值给该成员变量,以完成依赖注入
                        Object object = beanFactory.get(field.getType().getTypeName());
                        //通过Field（反射）为成员变量赋值：
                        field.setAccessible(true);
                        field.set(entry.getValue(), object);
                    }
                }
            }
        }
    }

    /**
     * 实例化： 放到loadedClazzlist集合中的Class对象都是需要做实例化的(加了@Component注解的类)
     *
     * @param loadedClazzList
     */
    private static void newInstance(HashMap<String, Class> loadedClazzList) throws InstantiationException, IllegalAccessException {
        for (Map.Entry<String, Class> entry : loadedClazzList.entrySet()) {
            beanFactory.put(entry.getKey(), entry.getValue().newInstance());
        }
    }

    /**
     * 加载指定路径下的类
     *
     * @param file
     * @return
     */
    private static HashMap<String, Class> loadAllClazz(File file) throws MalformedURLException, ClassNotFoundException {
        HashMap<String, Class> loadedClazzList = new HashMap<>();

        URL[] urls = new URL[]{file.toURI().toURL()};
        URLClassLoader classLoader = new URLClassLoader(urls);

        ArrayList<String> allCompleteClassName = getAllCompleteClassName(file);

        for (String element : allCompleteClassName) {
            Class<?> clazz = classLoader.loadClass(element);
            //拿到 Class 对象的时候，就进行筛选出有注解的 Class 再放到容器中，而不是把指定路径下的所有类都加载进来。
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Component.class) {
                    //得到带有 Component 注解的类对象了
                    loadedClazzList.put(element, clazz);
                }
            }
        }

        return loadedClazzList;
    }

    /**
     * 得到 file 中所有要加载的 class 文件的全类名
     *
     * @param file
     * @return
     */
    private static ArrayList<String> getAllCompleteClassName(File file) {
        // 所有要加载的class的全类名，如：classLoader.a02myclassloader.bean.Bean
        ArrayList<String> completeClassNames = new ArrayList<>();
        // 用于存放指定路径下所有要加载的class文件
        ArrayList<File> allNeedLoadClassFiles = new ArrayList<>();

        getAllNeedLoadClassFile(file, allNeedLoadClassFiles);

        for (File element : allNeedLoadClassFiles) {
            String filePath = element.getPath().replace("\\", ".");

            if (filePath.endsWith(".class")) {
                String completeClassName = filePath.substring(filePath.lastIndexOf("com."), filePath.lastIndexOf(".class"));
                completeClassNames.add(completeClassName);
            }
        }

        return completeClassNames;
    }

    /**
     * 通过递归获取指定路径下所有要加载的class文件
     *
     * @param file
     * @param allNeedLoadClassFiles
     */
    private static ArrayList<File> getAllNeedLoadClassFile(File file, ArrayList<File> allNeedLoadClassFiles) {
        if (!file.exists()) {
            return allNeedLoadClassFiles;
        }

        //是文件夹
        if (file.isDirectory()) {
            File[] listFiles = file.listFiles();
            for (File element : listFiles) {
                getAllNeedLoadClassFile(element, allNeedLoadClassFiles);
            }
        } else {
            //是文件
            allNeedLoadClassFiles.add(file);
        }
        return allNeedLoadClassFiles;
    }

}
